<?php

/**
 * 微信公众平台高级接口类
 * @author 雨中歌者
 * @copyright utuo tech
 */
namespace Utuo;

use Utuo\WechatAdvance\Media;

class WechatAdvance {
	// appid
	private $appId;
	// app secret
	private $appSecret;
	// 错误信息
	private $errMsg = null;
	// 错误代码
	private $errCode = null;
	// 是否启用缓存
	private $cacheEnabled = true;
	
	// 基础支持access缓存键
	const BASE_ACCESS_KEY = '__wechat_base_access';
	
	// 访问票缓存键名
	const TICKET_KEY = '__wechat_ticket';
	
	// 网页授权access缓存键
	const AUTH_ACCESS_KEY = '__wechat_auth_access';
	
	// constructor
	public function __construct($appId, $appSecret) {
		$this->appId = $appId;
		$this->appSecret = $appSecret;
		if (empty($appId) or empty($appSecret)) {
		    $this->setError("未配置微信开发者参数"); 
		}
	}
	
	// set error msg
	protected function setError($error) {
		$this->errMsg = $error;
	}
	
	// get error msg
	public function getError() {
		return $this->errMsg;
	}
	
	// set error msg
	protected function setErrorCode($error) {
		$this->errCode = $error;
	}
	
	// get error msg
	public function getErrorCode() {
		return $this->errCode;
	}
	
	/**
	 * 设置是否启用缓存
	 *
	 * @param boolean $cacheEnabled
	 */
	public function setCacheEnabled($cacheEnabled) {
		$this->cacheEnabled = !!$cacheEnabled;
	}
	
	/**
	 * 是否启用缓存
	 *
	 * @return boolean
	 */
	public function isCacheEnabled() {
		return $this->cacheEnabled;
	}
	
	/**
	 * 获取基础支持access_token
	 *
	 * @return false array
	 */
	public function getBaseAccess() {
		if ( $this->isCacheEnabled() ) {
		    $lastOpenTime = 1437464358; // 最后开启缓存时间 
			// 启用缓存
			$access = S(self::BASE_ACCESS_KEY.$this->appId);
			if (empty($access) || empty($access['access_token']) || (time() - $access['cache_time'] > ($access['expires_in']-120)) || ($lastOpenTime > $access['cache_time']) ) {
				// 缓存不存在或者已经过期，重新获取
				$access = $this->getAccessToken();
				if ($access) {
					$access['cache_time'] = time();
					S(self::BASE_ACCESS_KEY.$this->appId, $access, $access['expires_in']-120);
					/*SystemInfo::log("ACCESS_TOKEN_GET", array(
					   'access' => $access,
					   'appId' => $this->appId
					));*/
				}
			}
			return $access;
		} else {
			// 未启用缓存
			return $this->getAccessToken();
		}
	}
	
	
	/**
	 * 获取基础支持access_token(有缓存)
	 * @param $type jsapi/wx_card
	 * @return false array
	 */
	public function getJsApiTicket( $type = 'jsapi' ) {
		if ($this->isCacheEnabled()) {
			// 启用缓存
			$ticket = S(self::TICKET_KEY.$this->appId);
			if ( empty($ticket) || empty($ticket['ticket']) || (time()-$ticket['cache_time'] > $ticket['expires_in']-120) ) {
				// 缓存不存在或者已经过期，重新获取
				$ticket = $this->getTicket( $type );
				if ( $ticket ) {
					$ticket['cache_time'] = time();
					S(self::TICKET_KEY.$this->appId."_{$type}", $ticket);
				}
			}
			return $ticket;
		} else {
			// 未启用缓存
			return $this->getTicket( $type );
		}
	}
	
	/**
	 * 获取JsApi Ticket
	 * (ticket有效期目前为2个小时，需要定时去刷新，切重复获取将导致上次获取的ticket失效。)
	 * @return array
	 * array('access_token')
	 */
	public function getTicket( $type = 'jsapi' ) {
		$access = $this->getBaseAccess();
		$format = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?type=%s&access_token=%s";
		$url = sprintf($format, $type, $access['access_token']);
		return $this->getUrlContent($url);
	}
	
	/**
	 * 生成页面JSAPI调用签名包
	 * @return multitype:string number NULL
	 */
	public function getSignPackage() {
		$jsapiTicket = $this->getJsApiTicket("jsapi");
		$url = "http://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]";
		$timestamp = time();
		$nonceStr = $this->createNonceStr();
		// 这里参数的顺序要按照 key 值 ASCII 码升序排序
		$string = "jsapi_ticket={$jsapiTicket['ticket']}&noncestr={$nonceStr}&timestamp={$timestamp}&url={$url}";
		$signature = sha1($string);
		$signPackage = array(
				"appId"     => $this->appId,
				"nonceStr"  => $nonceStr,
				"timestamp" => $timestamp,
				"url"       => $url,
				"signature" => $signature,
				"rawstring" => $string,
		);
		return $signPackage;
	}
	
	/**
	 * 卡券签名包
	 * @param string $cardId n 卡券ID对应一类卡券 例如： 创建50元代金券，获取一个卡券ID，设置库存100万。则在卡券投放时可投放100万个Code码，展示在用户领取的卡券详情页。
	 * @param string $cardType * GROUPON 团购券类型 CASH 代金券类型 DISCOUNT 折扣券类型 GIFT 礼品券类型 GENERAL_COUPON 优惠券类型
	 * @param string $locationId n
	 * @return string
	 */
	public function getChooseCardPackage( $cardType = 'CASH', $cardId = '', $locationId = '' ) {
		$timestamp = time();
		$jsapiTicket = $this->getJsApiTicket("wx_card");
		$nonceStr = $this->createNonceStr();
		$data = array(
			'card_id' => $cardId, // n
			'location_id' => $locationId, // n
			'card_type' => $cardType, // *
			'app_id' => $this->appId, // *
			'api_ticket' => $jsapiTicket['ticket'],// *
			'time_stamp' => $timestamp, // *
			'nonce_str' => $nonceStr, // *
		);
		$values = array_values($data);
		sort($values, SORT_STRING);
		$string = sha1(implode($values));
		$signPackage = array_merge($data, array(
			'string' => implode($values),
			'signature' => $string,
		));
		return $signPackage;
	}
	
	/**
	 * 获取网页授权access_token
	 * 
	 * @param string $code
	 *
	 * @return false array
	 * 
	 * 缓存不可公用
	 */
	/*public function getAuthAccess($code) {
		if ($this->isCacheEnabled()) {
			// 启用缓存
			$access = S(self::AUTH_ACCESS_KEY.$this->appId);
			if (empty($access) || empty($access['access_token']) || (time() - $access['cache_time'] > $access['expires_in'])) {
				// 缓存不存在或者已经过期，重新获取
				$access = $this->getAuthAccessToken($code);
				if ($access) {
					$access['cache_time'] = time();
					S(self::AUTH_ACCESS_KEY.$this->appId, $access);
				}
			}
			return $access;
		} else {
			// 未启用缓存
			return $this->getAuthAccessToken($code);
		}
	}*/
	
	/**
	 * 获取access_token
	 * (access_token有效期目前为2个小时，需要定时去刷新，切重复获取将导致上次获取的access_token失效。)
	 * @return array
	 * array('access_token')
	 */
	private function getAccessToken() {
		$format = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s";
		$url = sprintf($format, $this->appId, $this->appSecret);
		return $this->getUrlContent($url);
	}
	
	
	/**
	 * 获取用户基础信息
	 *
	 * @param string $accessToken
	 * @param string $openId
	 * @return array
	 */
	public function getUserInfo($accessToken, $openId) {
		$format = "https://api.weixin.qq.com/cgi-bin/user/info?access_token=%s&openid=%s&lang=zh_CN";
		$url = sprintf($format, $accessToken, $openId);
		$userInfo = $this->getUrlContent($url);
		return $userInfo;
	}
	
	/**
	 * 获取网页授权跳转地址
	 *
	 * @param string $redirectUrl
	 * @param integer $scope 0=base,1=userinfo
	 * @param string $state
	 * @return string
	 */
	public function getAuthRedirectUrl($redirectUrl, $scope, $state) {
		$format = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=%s&redirect_uri=%s&response_type=code&scope=%s&state=%s#wechat_redirect";
		$scopeString = $scope == 1 ? "snsapi_userinfo" : "snsapi_base";
		return sprintf($format, $this->appId, urlencode($redirectUrl), $scopeString, $state);
	}
	
	/**
	 * 获取网页授权信息
	 * @return mixed
	 * array(
	 * 	"access_token" => "OezXcEiiBSKSxW0eoylIeAsR0GmYd1awCffdHgb4fhS_KKf2CotGj2cBNUKQQvj-G0ZWEE5-uBjBz941EOPqDQy5sS_GCs2z40dnvU99Y5AI1bw2uqN--2jXoBLIM5d6L9RImvm8Vg8cBAiLpWA8Vw",
	 *  "expires_in" => 7200,
	 *  "refresh_token" => "OezXcEiiBSKSxW0eoylIeAsR0GmYd1awCffdHgb4fhS_KKf2CotGj2cBNUKQQvj-G0ZWEE5-uBjBz941EOPqDQy5sS_GCs2z40dnvU99Y5CZPAwZksiuz_6x_TfkLoXLU7kdKM2232WDXB3Msuzq1A",
	 *  "openid" => "oLVPpjqs9BhvzwPj5A-vTYAX3GLc",
	 *  "scope" => "snsapi_userinfo,"
	 * )
	 */
	public function getAuthAccessToken($code) {
		$format = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code";
		$url = sprintf($format, $this->appId, $this->appSecret, $code);
		$authInfo = $this->getUrlContent($url);
		return $authInfo;
	}
	
	/**
	 * 获取网页授权用户信息
	 *
	 * @param string $accessToken
	 * @param string $openId
	 * @return mixed
	 */
	public function getAuthUserInfo($accessToken, $openId) {
		$format = "https://api.weixin.qq.com/sns/userinfo?access_token=%s&openid=%s&lang=zh_CN";
		$url = sprintf($format, $accessToken, $openId);
		$userInfo = $this->getUrlContent($url);
		return $userInfo;
	}
	
	/**
	 * 检验网页授权access_token是否正确
	 *
	 * @param string $accessToken
	 * @param string $openId
	 * @param boolean $returnResult
	 * @return Ambigous <boolean, mixed>
	 */
	public function checkAuthAccessToken($accessToken, $openId, $returnResult = false) {
		$format = "https://api.weixin.qq.com/sns/auth?access_token=%s&openid=%s";
		$url = sprintf($format, $accessToken, $openId);
		$result = $this->getUrlContent($url);
		return $returnResult ? $result : ($result['errcode'] == 0);
	}
	
	/**
	 * 获取网页授权刷新access_token
	 *
	 * @param string $refreshToken
	 * @return array
	 */
	public function getAuthRefreshAccessToken($refreshToken) {
		$format = "https://api.weixin.qq.com/sns/oauth2/refresh_token?appid=%s&grant_type=refresh_token&refresh_token=%s";
		$url = sprintf($format, $this->appId, $refreshToken);
		$token = $this->getUrlContent($url);
		return $token;
	}
	
	/**
	 * 发送模板消息
	 *
	 * @param string $accessToken
	 * @param string $openId
	 * @param string $templateId
	 * @param array $data
	 * @param string $url
	 * @param string $topColor
	 */
	public function sendTemplateMessage($accessToken, $openId, $templateId, $data, $url = '', $topColor = "#ff0000") {
		$format = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=%s";
		$postUrl = sprintf($format, $accessToken);
		$array = array (
				'touser' => $openId,
				'template_id' => $templateId,
				'url' => $url,
				'topcolor' => $url,
				'data' => $data 
		);
		return $this->getUrlContent($this->post($postUrl, json_encode($array)));
	}
	
	/**
	 * 发送客服消息
	 * 
	 * @param string $accessToken
	 * @param string $openId
	 * @param array $data
	 * 文本消息:
	 * array( "content" => "Hello World" )
	 * 
	 * 图片消息/语音消息:
	 * array( "media_id" => "MEDIA_ID" )
	 * 
	 * 视频消息:
	 * array( "media_id" => "MEDIA_ID" , "thumb_media_id" => "MEDIA_ID", "title" => "TITLE", "description" => "DESCRIPTION" )
	 * 
	 * 音乐消息:
	 * array( "title" => "MUSIC_TITLE" , "description" => "MUSIC_DESCRIPTION", "musicurl" => "MUSIC_URL", "hqmusicurl" => "HQ_MUSIC_URL", "thumb_media_id" => "THUMB_MEDIA_ID" )
	 * 
	 * 图文消息:
	 * array(
	 * 	  "articles" => array(
	 * 			0 => array(
	 * 				"title" => "Happy Day",
	 * 				"description" => "Is Really A Happy Day",
	 * 				"url" => "URL",
	 * 				"picurl" => "PIC_URL",
	 * 	   		),
	 * 			1 => array(
	 * 				"title" => "Happy Day",
	 * 				"description" => "Is Really A Happy Day",
	 * 				"url" => "URL",
	 * 				"picurl" => "PIC_URL",
	 * 	   		),
	 * 	   )
	 * )
	 * 
	 * @param array
	 */
	public function sendServerMessage( $accessToken, $openId, $data, $type = 0 ) {
		$types = array(
			0 => 'text', 
			1 => 'image',
			2 => 'voice',
			3 => 'video',
			4 => 'music',
			5 => 'news',
		);
		$format = "https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=%s";
		$postUrl = sprintf($format, $accessToken);
		$sendData = array(
			'touser' => $openId,
			'msgtype' => $types[$type],
		);
		$sendData[$types[$type]] = $data;
		return $this->getDataContent($this->post($postUrl, \Utuo\Wechat::json_encode($sendData)), true) ;
	}
	
	/**
	 * 创建二维码与Ticket
	 * @param array $sendData 
	 * array(
	 * 	  'expire_seconds' => '', // 该二维码有效时间，以秒为单位。 最大不超过1800。
	 *    'action_name' => '' // 二维码类型，QR_SCENE为临时,QR_LIMIT_SCENE为永久,QR_LIMIT_STR_SCENE为永久的字符串参数值
	 *    'action_info' => array(), //* 二维码详细信息
	 *    'scene_id' => 1, //* 场景值ID，临时二维码时为32位非0整型，永久二维码时最大值为100000（目前参数只支持1--100000）
	 *    'scene_str' => '', // 场景值ID（字符串形式的ID），字符串类型，长度限制为1到64
	 * )
	 * @return array
	 * array(2) {
	 * 	  ["ticket"] => string(96) "gQEV8ToAAAAAAAAAASxodHRwOi8vd2VpeGluLnFxLmNvbS9xLzduWDFuQVhsLVZHRUhTMlJXbG5tAAIEg3TAVAMEAAAAAA=="
	 * 	  ["url"] => string(43) "http://weixin.qq.com/q/7nX1nAXl-VGEHS2RWlnm", // 二维码地址，可跟据此地址生成二维码 也可用getQrCode( $ticket )
	 * }
	 */
	public function getQrTicket($type, $accessToken, $data ) {
		$default = array(
			'action_name' => $type,
			'scene_str' => $this->createNonceStr(60),
		);
		($data and $data = array_merge($default, $data)) or $data = $default;
		$format = "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=%s";
		$postUrl = sprintf($format, $accessToken);
		return $this->getDataContent($this->post($postUrl, \Utuo\Wechat::json_encode($data)), true) ;
	}
	
	/**
	 * 通过ticket得到二维码
	 * @return array
	 * array(
	 * 	'header' => array(
	 * 		// ... ...
	 * 	),
	 * 	'content' => '图片二进制数据'
	 * ) 
	 */
	public function getQrCode ( $ticket ) {
		$format = "https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=%s";
		$postUrl = sprintf($format, $ticket);
		$result = Http::get($postUrl, true);
		return $result;
	}
	
	/**
	 * 上传多媒体文件
	 * @param $data 文件路径
	 * array( 'content' => '二进制内容', 'mime' => '' )
	 * @param $type 上传类型
	 */
	public function mediaUpload( $data, $type = Media::MEDIA_TYPE_IMAGE ) {
		$format = "http://file.api.weixin.qq.com/cgi-bin/media/upload?access_token=%s&type=%s"; 
		$access = $this->getBaseAccess();
		$postUrl = sprintf($format,  $access['access_token'], $type );
		$mimeArr = explode('/', $data['mime']);
		$fileName = time().'.'.$mimeArr[1]; 
		$key = "media\"; filename=\"$fileName\r\nContent-Type: {$data['mime']}\r\n";
		$data[$key] = $data['content'];
		$result = Http::post($postUrl, $data);
		return $this->getDataContent($result);
	}
	
	/**
	 * 读取返回结果，转化成数组
	 * 
	 * @param unknown $result
	 * @return boolean|unknown
	 */
	private function getDataContent( $result ) {
		$result = json_decode($result, true);
		if (!$result || !empty($result['errcode'])) {
			$this->_on_error_get_content($result);			
			return false;
		}
		return $result;
	}
	
	/**
	 * 读取页面返回结果，转化成数组
	 *
	 * @param string $url
	 * @return mixed
	 */
	private function getUrlContent($url) {
		$json = $this->getFileContent($url);
		$result = json_decode($json, true);
		if (!$result || !empty($result['errcode'])) {
			$this->_on_error_get_content($result);
			return false;
		}
		return $result;
	}
	
	/**
	 * 获得内容错误回调
	 * @param unknown $result
	 */
	private function _on_error_get_content( $result ) {
		$this->setResultError( !$result ? array(
				'errcode' => -1,
				'errmsg' => "unknow error.", // access for <a href=\"".$url."\">$url</a>
		) : $result );
		
// 		$trace = debug_backtrace();
// 		$debugInfo = array(
// 				'result' => $result,
// 				'trace' => $trace,
// 		);
// 		SystemInfo::log("WECHAT_API_ERROR", $debugInfo);
		
		if ( $result['errcode'] == '40001' ) { // base token 超时
			$access = S(self::BASE_ACCESS_KEY.$this->appId);
			if ( $access ) {
				SystemInfo::log("ACCESS_TOKEN_OUT_TIME_ERROR", array(
					'using_time' => time() - $access['cache_time']
				));
				S(self::BASE_ACCESS_KEY.$this->appId, null);
			}
		}
	}
	
	/**
	 * 获得错误信息
	 *
	 * @param unknown $result
	 */
	private function setResultError( $result ) {
		if ( !empty($result['errcode'] ) ) {
			$this->errCode = $result['errcode'];
			$this->errMsg = $result['errmsg'];
		}
	}
	
	
	/**
	 * 获取url内容
	 *
	 * @param string $url
	 * @return string
	 */
	private function getFileContent( $url ) {
		$ch = curl_init($url);
		curl_setopt($ch, CURLOPT_HEADER, 0);
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
		curl_setopt($ch, CURLOPT_POST, false);
		curl_setopt	($ch, CURLOPT_SSL_VERIFYPEER, false);
		curl_setopt	($ch, CURLOPT_SSL_VERIFYHOST, false);
		$content = curl_exec($ch);
		curl_close($ch);
		return $content;
	}
	
	/**
	 * POST提交数据
	 *
	 * @param string $url
	 * @param array $data
	 * @return mixed
	 */
	private function post($url, $data) {
		return Http::post($url, $data);
	}
	
	/**
	 * 随机字符串
	 * @param number $length
	 * @return string
	 */
	private function createNonceStr($length = 16) {
		$chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
		$str = "";
		for ($i = 0; $i < $length; $i++) {
			$str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
		}
		return $str;
	}
}